/*******************************************************************************
 * Copyright (c) 2016 Herwig Eichler, www.conmeleon.org
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Herwig Eichler  - initial API and implementation and initial documentation
 *******************************************************************************/

#include "gpiopin.h"
#include "../util/fileres.h"
#include <string>
#include <iostream>
#include "forte/util/criticalregion.h"

namespace CONMELEON {

  CPCSyncObject CGpioPin::m_GlobalFileMutex;

  CGpioPin::CGpioPin(int iPinNr, EPinDirection enDir) :
      m_Nr(iPinNr),
      m_Valid(iPinNr > 0),
      m_Inverted(false),
      m_Direction(enDir),
      m_State(unused) {

    if (iPinNr > 0) {
      m_Valid = this->sysfsExportPin() && this->sysfsSetPinDirection(enDir) && this->sysfsOpenValueFileStream(enDir);
    }
  }

  CGpioPin::~CGpioPin() {

    this->sysfsUnexportPin();
    // the value file stream does not need to be closed here, this will be handled by the fstream destructor.
  }

  bool CGpioPin::sysfsExportPin() const {

    char szPinNr[12] = {0};

    sprintf(szPinNr, "%d", m_Nr);

    /* protect sysfs export file from multiple access
     * all class instances need access to the same export file, that's why a global class mutex needs to be used
     */
    bool bRet;
    {
      CCriticalRegion criticalRegion(m_GlobalFileMutex);
      bRet = writeToFile(ExportFilePath, &szPinNr[0]);
    }

    return bRet;

    // TODO: if an exception is thrown within writeToFile(), the mutex might be left locked
  }

  bool CGpioPin::sysfsUnexportPin() const {

    char szPinNr[12] = {0};

    sprintf(szPinNr, "%d", m_Nr);

    bool bRet;
    {
      CCriticalRegion criticalRegion(m_GlobalFileMutex);
      bRet = writeToFile(UnexportFilePath, &szPinNr[0]);
    }

    return bRet;
  }

  bool CGpioPin::sysfsSetPinDirection(EPinDirection enDir) {

    char szFilename[40] = {0};
    bool bRet = false;

    sprintf(szFilename, "%s%d%s", SignalFilePathPrefix, m_Nr, DirFilePathPostfix);

    /* only this class instance needs access to the sysfs direction file
     * we can use the local mutex for protection
     */
    {
      CCriticalRegion criticalRegion(m_LocalFileMutex);

      if ((enDir == input) && (writeToFile(&szFilename[0], "in"))) {
        bRet = true;
      }

      if ((enDir == output) && (writeToFile(&szFilename[0], "out"))) {
        bRet = true;
      }
    }

    return bRet;
  }

  bool CGpioPin::sysfsOpenValueFileStream(EPinDirection enDir) {

    char szFilename[40] = {0};

    sprintf(szFilename, "%s%d%s", SignalFilePathPrefix, m_Nr, ValFilePathPostfix);

    /* only this class instance needs access to the sysfs value file
     * we can use the local mutex for protection
     */
    {
      CCriticalRegion criticalRegion(m_LocalFileMutex);

      m_PinValueStream.open(szFilename, ((enDir == input) ? std::ios_base::in : std::ios_base::out));
    }

    return m_PinValueStream.is_open();
  }

  void CGpioPin::setInverted(bool bInverted) {

    m_Inverted = bInverted && (m_Direction == input);
  }

  bool CGpioPin::read() const {

    if (m_Valid && (m_Direction == input)) {

      if (m_PinValueStream.is_open()) {

        std::string sLine;

        CCriticalRegion criticalRegion(m_LocalFileMutex);

        /* set to beginning of file, just to be sure */
        m_PinValueStream.clear();
        m_PinValueStream.seekg(0, std::ios::beg);

        m_PinValueStream >> sLine;

        return ((sLine != "0") ^ (m_Inverted));
      }
    }
    return false;
  }

  void CGpioPin::write(bool bValue) {

    if (m_Valid && (m_Direction == output)) {
      CCriticalRegion criticalRegion(m_LocalFileMutex);
      if (m_PinValueStream.is_open()) {
        m_PinValueStream << (bValue ? "1" : "0") << std::flush;
      }
    }
  }

  bool readFromFile(const char *pszFileName, char *pszContent, std::size_t nBufferLength) {

    if ((pszFileName == nullptr) || (pszContent == nullptr) || (nBufferLength < 1)) {
      return false;
    }
    CFileResource file(pszFileName, "r");

    if (file.isOpen()) {
      file.readLine(pszContent, nBufferLength);
      // TODO:  add error handling
      return true;
    } else {
      *pszContent = '\0';
      return false;
    }

    // no need to close the file manually, because this is handled by the destructor of CFileResource class (RAII)
  }

  bool writeToFile(const char *pszFileName, const char *pszContent) {

    if (pszFileName == nullptr) {
      return false;
    }
    CFileResource file(pszFileName, "a");

    if (file.isOpen() && (pszContent != nullptr)) {
      file.writeLine(pszContent);
      // TODO:  add error handling
    }
    return true;
  }

} // namespace CONMELEON
